package cn.edu.hnu.matching.utils;

import cn.edu.hnu.matching.model.dto.UserRecommendSortDto;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 数据推荐工具类
 */
public class RecommendUtil {

    private RecommendUtil() {}

    /**
     * 标签分类数据
     */
    private static final String[][][] TAG_CATEGORIES = {
            { // 兴趣爱好 (index 0)
                    {"运动类", "篮球", "足球", "羽毛球", "跑步", "健身", "游泳", "滑雪", "瑜伽"},
                    {"音乐类", "摇滚", "古典", "电子", "流行", "KPOP", "嘻哈", "爵士", "民谣"},
                    {"游戏类", "英雄联盟", "DOTA2", "王者荣耀", "原神", "Switch", "Steam", "桌游"},
                    {"影视类", "科幻", "悬疑", "恐怖", "动漫", "漫威", "DC", "日剧", "韩剧", "美剧"},
                    {"书籍类", "科幻小说", "推理小说", "文学", "心理学", "历史", "哲学", "经济学"},
            },
            { // 技术领域 (index 1)
                    {"编程语言", "Java", "Python", "Go", "C++", "Rust", "JavaScript", "TypeScript"},
                    {"技术方向", "前端", "后端", "全栈", "大数据", "人工智能", "嵌入式", "游戏开发"},
                    {"云计算", "Docker", "Kubernetes", "AWS", "阿里云", "腾讯云", "DevOps"},
                    {"数据分析", "SQL", "Pandas", "Machine Learning", "Deep Learning"},
                    {"移动开发", "Android", "iOS", "Flutter", "React Native"},
            },
            { // 生活方式 (index 2)
                    {"饮食偏好", "素食主义", "奶茶爱好者", "咖啡控", "甜食爱好者", "无辣不欢"},
                    {"旅游偏好", "城市游", "自然风光", "海岛度假", "徒步旅行", "自驾游", "背包客"},
                    {"作息习惯", "早睡早起", "夜猫子", "熬夜党", "晨跑爱好者"},
            },
            { // 学习生活 (index 3)
                    {"在读学历", "本科", "研究生", "博士"},
                    {"学习方向", "计算机", "经济学", "心理学", "哲学", "法律", "医学"},
                    {"职业领域", "互联网", "金融", "教育", "法律", "医疗", "自由职业"},
            },
            { // MBTI (index 4)
                    {"人格类型", "ESFP", "ISFP", "ENFJ", "ENFP", "ESTP", "ISTP", "INFJ",
                            "INFP", "ESFJ", "ISFJ", "ENTP", "INTP", "ESTJ", "ISTJ", "ENTJ", "INTJ"},
            }
    };

    /**
     * 粗排分类映射
     */
    private static final Map<String, Integer> TAGS_MAP = new HashMap<>();
    /**
     * 粗排子分类映射
     */
    private static final Map<String, Integer> SUB_TAGS_MAP = new HashMap<>();

    static {
        // 初始化两个排序的映射
        initTagsMap();
        initSubTagsMap();
    }

    /**
     * 第一次粗排后的剩余容量
     */
    private static final Integer MAX_NUM_OF_TAGS = 25;

    /**
     * 第二次粗排后的剩余容量
     */
    private static final Integer MAX_NUM_OF_SUB_TAGS = 13;

    /**
     * 精排后的剩余容量
     */
    private static final Integer MAX_NUM_OF_FINE = 5;

    /**
     * 初始化粗排分类映射
     */
    private static void initTagsMap() {
        // 遍历每个大分类及其子分类和标签
        for (int categoryIndex = 0; categoryIndex < TAG_CATEGORIES.length; categoryIndex++) {
            for (String[] subCategory : TAG_CATEGORIES[categoryIndex]) {
                // 第一个元素是子分类名称，其余元素是标签
                for (int i = 1; i < subCategory.length; i++) {
                    TAGS_MAP.put(subCategory[i], categoryIndex);
                }
            }
        }
    }

    /**
     * 初始化粗排子分类映射
     */
    private static void initSubTagsMap() {
        // 遍历每个大分类及其子分类和标签
        int index = 0;
        for (String[][] tagCategory : TAG_CATEGORIES) {
            for (String[] subCategory : tagCategory) {
                // 第一个元素是子分类名称，其余元素是标签
                for (int i = 1; i < subCategory.length; i++) {
                    SUB_TAGS_MAP.put(subCategory[i], index);
                }
                ++index;    // 每个数组遍历完后，形成一个映射
            }
        }
    }

    /**
     * 把文字标签根据大标签分类进行转换
     * @param tags 文字标签
     * @return 转换后的大标签分类列表
     */
    private static Set<Integer> turnTagsToIndex(List<String> tags) {
        return tags.stream().map(tag -> TAGS_MAP.getOrDefault(tag, -1)).collect(Collectors.toSet());
    }

    /**
     * 把文字标签根据子标签分类进行转换
     * @param tags 文字标签
     * @return 转换后的子标签分类列表
     */
    private static Set<Integer> turnTagsToSubIndex(List<String> tags) {
        return tags.stream().map(tag -> SUB_TAGS_MAP.getOrDefault(tag, -1)).collect(Collectors.toSet());
    }

    /**
     * 根据大标签粗排，该方法会直接把大标签的交集数量作为打分标准
     * @param users 被粗排的用户列表
     * @param currentUser 当前用户
     * @return 粗排后的列表
     */
    public static List<UserRecommendSortDto> roughSortByTag(List<UserRecommendSortDto> users, UserRecommendSortDto currentUser) {
        // 准备好当前用户的标签列表
        Set<Integer> currentUserTagsIndex = turnTagsToIndex(currentUser.getTags());

        for (UserRecommendSortDto dto : users) {
            // 先把用户的标签根据大标签来转换
            Set<Integer> tagsIndex = turnTagsToIndex(dto.getTags());
            tagsIndex.retainAll(currentUserTagsIndex);
            dto.setRoughByTagsScore(tagsIndex.size());
        }

        // 直接返回所有
        return users;
    }

    /**
     * 根据子标签粗排，该方法会直接把子标签的交集数量作为打分标准
     * @param users 被粗排的用户列表
     * @param currentUser 当前用户
     * @return 粗排后的列表
     */
    public static List<UserRecommendSortDto> roughSortBySubTag(List<UserRecommendSortDto> users, UserRecommendSortDto currentUser) {
        // 准备好当前用户的标签列表
        Set<Integer> currentUserSubTagsIndex = turnTagsToSubIndex(currentUser.getTags());

        for (UserRecommendSortDto dto : users) {
            // 先把用户的标签根据子标签来转换
            Set<Integer> subTagsIndex = turnTagsToSubIndex(dto.getTags());
            subTagsIndex.retainAll(currentUserSubTagsIndex);
            dto.setRoughBySubTagsScore(subTagsIndex.size());
        }

        // 直接返回所有
        return users;
    }

    /**
     * 根据余弦相似度精排，该方法会计算余弦相似度后排序
     * @param users 被精排的用户列表
     * @param currentUser 当前用户
     * @return 精排后的列表
     */
    public static List<UserRecommendSortDto> fineSort(List<UserRecommendSortDto> users, UserRecommendSortDto currentUser) {
        // 优先队列，TopN 问题，找前 N 个最大的，用小顶堆
        Comparator<UserRecommendSortDto> comparator = Comparator.comparingDouble(dto ->
                dto.getFineScore() + dto.getRoughByTagsScore() + dto.getRoughBySubTagsScore());

        PriorityQueue<UserRecommendSortDto> queue = new PriorityQueue<>(comparator);

        List<String> currentUserTags = currentUser.getTags();

        // 直接精排了
        for (UserRecommendSortDto dto : users) {
            double similarity = cosineSimilarity(dto.getTags(), currentUserTags);
            dto.setFineScore(similarity);
            // 得到粗排分数后，放入队列，保证队列数量
            queue.add(dto);
            if (queue.size() > MAX_NUM_OF_FINE) {
                queue.poll();
            }
        }

        // 得到 TopN 后返回列表
        return queue.stream().toList();
    }

    /**
     * 余弦相似度计算
     * @param tags1 标签1
     * @param tags2 标签2
     * @return 相似度
     */
    public static double cosineSimilarity(List<String> tags1, List<String> tags2) {
        if (tags1 == null || tags2 == null) {
            return 0.0;
        }
        if (tags1.isEmpty() || tags2.isEmpty()) {
            return 0.0;
        }
        // 所有标签的并集作为向量空间
        Set<String> allTags = new HashSet<>(tags1);
        allTags.addAll(tags2);
        // 向量构建
        Map<String, Integer> vec1 = new HashMap<>();
        Map<String, Integer> vec2 = new HashMap<>();
        for (String tag : allTags) {
            vec1.put(tag, tags1.contains(tag) ? 1 : 0);
            vec2.put(tag, tags2.contains(tag) ? 1 : 0);
        }
        // 点积、模长
        int dotProduct = 0;
        double normA = 0.0, normB = 0.0;
        for (String tag : allTags) {
            int a = vec1.get(tag);
            int b = vec2.get(tag);
            dotProduct += a * b;
            normA += a * a;
            normB += b * b;
        }
        if (normA == 0 || normB == 0) {
            return 0.0;
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)) * 100;
    }

}
